package com.sfac.springboot.modules.account.service.impl;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import javax.transaction.Transactional;

import com.sfac.springboot.modules.account.dao.UserDao;
import com.sfac.springboot.modules.account.dao.UserRoleDao;
import com.sfac.springboot.modules.account.entity.User;
import com.sfac.springboot.modules.account.entity.UserRole;
import com.sfac.springboot.modules.account.service.UserService;
import com.sfac.springboot.modules.common.vo.Result;
import com.sfac.springboot.modules.common.vo.Search;
import com.sfac.springboot.utils.MD5Util;
import com.sfac.springboot.utils.RedisUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

/**
 * @Description: User Service Impl
 * @author: HymanHu
 * @date: 2021年2月21日
 */
@Service
public class UserServiceImpl implements UserService {

    public static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);

    @Autowired
    private UserDao userDao;
    @Autowired
    private UserRoleDao userRoleDao;
    @Autowired
    private RedisUtils redisUtils;

    @Override
    public User getUserByUserNameAndPassword(String userName, String password) {
        return userDao.getUserByUserNameAndPassword(userName, MD5Util.getMD5(password));
    }

    @Override
    public User getUserByUserName(String userName) {
        List<User> users = Optional
                .ofNullable(userDao.getUserByUserName(userName))
                .orElse(Collections.emptyList());
        return users.isEmpty() ? null : users.get(0);
    }

    @Override
    public Result<User> login(User user) {

        // 获取连续错误登录次数，超过 3 次，则返回错误消息，没超过 3 次，则继续登录
        String errorLoginKey = String.format("errorLoginCount%s", user.getUserName());
        int count = redisUtils.get(errorLoginKey) == null ? 0 : (Integer) redisUtils.get(errorLoginKey);
        if (count > 3) {
            return new Result<>(Result.ResultStatus.FAILD.code,
                    "已经连续错误登录 3 次，账户锁定 15 秒。");
        }

        //得到 subject
        Subject subject = SecurityUtils.getSubject();

        //封装一个登录令牌（装载用户名和密码）
        UsernamePasswordToken token = new UsernamePasswordToken(
                user.getUserName(),
                //密码从页面过来，没有加密，所以用MD5加密
                MD5Util.getMD5(user.getPassword()));

        try {
            //subject.login（令牌）
            // 身份认证和资源授权
            subject.login(token);
            subject.checkRoles();

            //获取当前用户，并将之设置到 session 中
            //在MyRealm中放的什么，这里就是什么
            User temp = (User) subject.getPrincipal();
            Session session = subject.getSession();
            session.setAttribute("user", temp);

            //将错误登录次数置零
            redisUtils.set(errorLoginKey, 0);

            return new Result<User>(Result.ResultStatus.SUCCESS.code, "Success");
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.debug(e.getMessage());

            //将错误登录次数自加 1
            redisUtils.increment(errorLoginKey, 1);

            //判断错误登录次数是否大于 3 次，是则设置该 key 过期时间
            count = redisUtils.get(errorLoginKey) == null ? 0 :
                    (Integer) redisUtils.get(errorLoginKey);

            String errorMsg = "";
            if (count < 3) {
                errorMsg = String.format("已经连续错误登录 %d 次，还有 %d 次机会", count, (3 - count));
            }else {
                errorMsg = "已经连续错误登录 3 次，锁定账户 15 秒";
                redisUtils.expire(errorLoginKey,15);
            }
            return new Result<User>(Result.ResultStatus.FAILD.code, errorMsg);
        }
    }


    @Override
    public void logout() {
        //得到subject
        Subject subject = SecurityUtils.getSubject();
        //清除session
        subject.logout();
    }

    @Override
    @Transactional
    public Result<User> insertUser(User user) {
        if (StringUtils.isBlank(user.getUserName()) ||
                StringUtils.isBlank(user.getPassword())) {
            return new Result<User>(Result.ResultStatus.FAILD.code,
                    "User Name or password is null.");
        }

        List<User> users = Optional
                .ofNullable(userDao.getUserByUserName(user.getUserName()))
                .orElse(Collections.emptyList());
        if (users.size() > 0) {
            return new Result<User>(Result.ResultStatus.FAILD.code,
                    "User Name or email is repeat.");
        }

        user.setCreateDate(LocalDateTime.now());
        user.setUpdateDate(LocalDateTime.now());
        user.setPassword(MD5Util.getMD5(user.getPassword()));
        userDao.insertUser(user);
        if (user.getRoles() != null) {
            user.getRoles().stream()
                    .forEach(item -> {
                        userRoleDao.insertUserRole(
                                new UserRole(user.getId(), item.getId()));
                    });
        }
        return new Result<User>(
                Result.ResultStatus.SUCCESS.code, "Insert success", user);
    }

    @Override
    @Transactional
    public Result<User> updateUser(User user) {
        List<User> users = Optional
                .ofNullable(userDao.getUserByUserNameOrEmail(
                        user.getEmail(), user.getUserName()))
                .orElse(Collections.emptyList());
        if (users.stream().filter(item -> item.getId() != user.getId()).count() > 0) {
            return new Result<User>(Result.ResultStatus.FAILD.code,
                    "User Name or email is repeat.");
        }

        userDao.updateUser(user);
        if (user.getRoles() != null && !user.getRoles().isEmpty()) {
            userRoleDao.deleteUserRoleByUserId(user.getId());
            user.getRoles().stream()
                    .forEach(item -> {
                        userRoleDao.insertUserRole(
                                new UserRole(user.getId(), item.getId()));
                    });
        }

        return new Result<User>(Result.ResultStatus.SUCCESS.code,
                "Update success", user);
    }

    @Override
    public User getUserById(int id) {
        return userDao.getUserById(id);
    }

    @Override
    @Transactional
    public Result<Object> deleteUserById(int id) {
        userDao.deleteUserById(id);
        userRoleDao.deleteUserRoleByUserId(id);
        return new Result<Object>(Result.ResultStatus.SUCCESS.code, "Delete success");
    }

    @Override
    public PageInfo<User> getUsersBySearchBean(Search search) {
        search.init();
        PageHelper.startPage(search.getCurrentPage(), search.getPageSize());
        return new PageInfo<User>(Optional
                .ofNullable(userDao.getUsersBySearchVo(search))
                .orElse(Collections.emptyList()));
    }

    @Override
    public List<User> getUsersByKeyWord(String keyword) {
        Search search = new Search();
        search.setKeyword(keyword);
        search.init();
        return Optional.ofNullable(userDao.getUsersBySearchVo(search))
                .orElse(Collections.emptyList());
    }
}
